异常处理

  当一个程序被设计为一集模块以后,对于错误的处理也必须在这些模块的基础之上考虑。哪个模块应该承担对于哪个错误的处理责任?常常遇到的情况是,检查出错误的模块并不知道应该去做些什么,恢复的动作依赖于那些调用操作的模块,而不是那个试图去执行操作并且发现错误的模块。随着程序不断增大,特别是随着各种库的广泛使用,处理错误(或者更一般的,“异常情形”)的标准方式也变得更加重要了。

  重新考虑有关Stack的例子。如果我们试图向堆栈中 push() 放进的字符过多,这时应该怎么办呢?写Stack的人不可能知道在这种情况下用户希望做些什么,而用户也未必能够始终如一地检查这个问题(如果真能这样做的话,溢出问题根本就不会出现了)。解决的办法就是让Stack的实现者去检查溢出问题,而后告诉那个(它并不知道是什么的)用户。这样就使用户可以采取适当的行动。例如,

    namespace Stack        // 界面
    {
        void push(char);
        char pop();

        class Overflow{ };    // 表示溢出异常的类型
    }

当检查到溢出之时,Stack::push() 可以激活异常处理代码;也就是说,它“抛出一个Overflow异常”:

    void Stack::push(char c)
    {
        if(top == max_size) throw Overflow();
        // 压入c
    }

这个 throw 将把控制传递给某个处理 Stack::Overflow 异常的处理器,该处理器应该位于某个直接或者间接调用了 Stack::push() 的函数里。在这个传递过程中,实现将根据需要退掉一部分函数调用栈,退回到能处理异常的那个调用函数的环境位置。可见,抛出的行为就像是一种多层的返回。例如,

    void f()
    {
        // ...
        try {
            while(true) Stack::push('c');
        }
        catch (Stack::Overflow) {
            // 呜呼,出现异常;执行适当动作
        }
        // ...
    }

这里的 while 循环将不断地做下去。当然,在某个对Stack::push() 的调用导致 throw 时,执行就将进入提供了针对 Stack::Overflow 的处理器的 catch 子句之中。

  采用异常处理机制能使对错误的处理更加规范,也使它更容易看清楚。

🔚